<!--
tinymce 富文本编辑器
@author zqy
@created 2021/12/26 10:56
-->
<template>
  <div>
    <div>
      <textarea :id="editorId" :key="tinymceFlag" />
      <!-- <editor
        ref="editor"
        v-model="content"
        :api-key="tinymceFlag"
        :init="editConfig"
        :disabled="false"
      /> -->
    </div>
  </div>
</template>

<script>
import Editor from '@tinymce/tinymce-vue'
import tinymce from 'tinymce/tinymce' // tinymce默认hidden，不引入则不显示编辑器
import 'tinymce/themes/silver' // 编辑器主题，不引入则报错
import 'tinymce/icons/default' // 引入编辑器图标icon，不引入则不显示对应图标

// 引入编辑器插件（基本免费插件都在这儿了）
import 'tinymce/plugins/advlist' // 高级列表
import 'tinymce/plugins/anchor' // 锚点
import 'tinymce/plugins/autolink' // 自动链接
import 'tinymce/plugins/autoresize' // 编辑器高度自适应,注：plugins里引入此插件时，Init里设置的height将失效
import 'tinymce/plugins/autosave' // 自动存稿
import 'tinymce/plugins/charmap' // 特殊字符
import 'tinymce/plugins/code' // 编辑源码
import 'tinymce/plugins/codesample' // 代码示例
import 'tinymce/plugins/directionality' // 文字方向
import 'tinymce/plugins/emoticons' // 表情
import 'tinymce/plugins/fullpage' // 文档属性
import 'tinymce/plugins/fullscreen' // 全屏
import 'tinymce/plugins/help' // 帮助
import 'tinymce/plugins/hr' // 水平分割线
import 'tinymce/plugins/image' // 插入编辑图片
import 'tinymce/plugins/importcss' // 引入css
import 'tinymce/plugins/insertdatetime' // 插入日期时间
import 'tinymce/plugins/link' // 超链接
import 'tinymce/plugins/lists' // 列表插件
import 'tinymce/plugins/media' // 插入编辑媒体
import 'tinymce/plugins/nonbreaking' // 插入不间断空格
import 'tinymce/plugins/pagebreak' // 插入分页符
import 'tinymce/plugins/paste' // 粘贴插件
import 'tinymce/plugins/preview'// 预览
import 'tinymce/plugins/print'// 打印
import 'tinymce/plugins/quickbars' // 快速工具栏
import 'tinymce/plugins/save' // 保存
import 'tinymce/plugins/searchreplace' // 查找替换
// import 'tinymce/plugins/spellchecker'  //拼写检查，暂未加入汉化，不建议使用
import 'tinymce/plugins/tabfocus' // 切入切出，按tab键切出编辑器，切入页面其他输入框中
import 'tinymce/plugins/table' // 表格
import 'tinymce/plugins/template' // 内容模板
import 'tinymce/plugins/textcolor' // 文字颜色
import 'tinymce/plugins/textpattern' // 快速排版
import 'tinymce/plugins/toc' // 目录生成器
import 'tinymce/plugins/visualblocks' // 显示元素范围
import 'tinymce/plugins/visualchars' // 显示不可见字符
import 'tinymce/plugins/wordcount' // 字数统计

import request from '@/utils/request'
import { formatURL } from './utils'
import { v4 as uuidv4 } from 'uuid'
export default {
  name: 'QUPTinyEditor',
  components: { Editor },
  model: {
    prop: 'content',
    event: 'change'
  },
  props: {
    /* 编辑器id, 多个编辑器时使用 */
    editorId: {
      type: String,
      default: `tiny-mce-editor-${uuidv4()}`
    },
    /* 编辑器初始化配置参数对象 */
    editorConfig: {
      type: Object,
      default() {
        return {}
      }
    },
    /* 编辑器模式，readonly(只读)/design(编辑) */
    mode: {
      type: String,
      default: 'design'
    },
    /* 富文本编辑器显示的内容 */
    content: {
      type: String,
      default: ''
    },
    placeholder: {
      type: String,
      default: '请输入'
    },
    /* 富文本编辑器初始显示内容 */
    initContent: {
      type: String,
      default: ''
    }, // TODO:是否必须存在
    /* 富文本中上传图片到图片库时额外附带的参数 */
    uploadData: {
      type: Object,
      default: () => {
        return {
          // scene: 'prodDetail',
          // source: 'TinyMCE Editor'
        }
      }
    }, // 上传时附带的额外参数
    imageAccept: {
      type: Array,
      default: () => {
        return ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
      }
    }, // 允许上传的图片类型
    videoAccept: {
      type: Array,
      default: () => {
        return ['.mp3', '.mp4', '.ogv']
      }
    }, // 允许上传的视频类型
    fileAccept: {
      type: Array,
      default: () => {
        return ['.zip', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
      }
    }, // 允许上传的文件类型

    filePrefix: {
      type: String,
      default: ''
    }, // 上传文件前缀
    imageSize: {
      type: Number,
      default: 2// 默认2M
    }, // 上传图片的大小限制
    beforeUpload: {
      type: Function,
      default: () => {
        return true
      }
    }, // 上传前的处理
    uploadResField: {
      type: String,
      default: 'file'
    }, // 上传返回字段
    uploadUrl: {
      type: String,
      default: '/api/v1/file/upload'
    }, // 上传url地址
    customButtons: {
      type: Array,
      default: () => [] // [{code,name,text,tooltip,onActive}]
    } // 自定义按钮
  },
  data() {
    return {
      editor: null,
      tinymceFlag: 1,
      dialogPicUpload: false,
      hasInit: false,
      hasChange: false,
      editConfig: {}
    }
  },
  watch: {
    mode: {
      handler(val) {
        this.setMode(val)
      }
    },
    // 由于双向绑定存在光标位置跳回首位问题，所以监听initContent参数
    initContent: {
      handler(val) {
        this.setContent(val)
      }
    }
  },
  created() {
    // this.initEidtor()
  },
  activated() {
    this.tinymceFlag++
  },
  mounted() {
    // window.vm = this
    /* tinymce.init({}).then((editors) => {
      this.editor = editors[0]
      if (this.content || this.initContent) {
        this.setContent(this.content || this.initContent)
      }
      // this.editor.show()
      this.setMode(this.mode)
    }).catch((error) => {
      this.$message.error('富文本编辑器初始化失败: ' + error)
    }) */
    this.initEidtor()
  },
  beforeDestroy() {
    if (this.editor) {
      this.editor.destroy()
    }
  },
  methods: {
    /**
     * 图库中选中图片后插入到富文本编辑器中
     * @param imgs
     */
    selectedPics(imgs) {
      // console.log(imgs)
      for (let i = 0; i < imgs.length; i++) {
        const pic = imgs[i]
        this.editor.insertContent(
          `<img alt="${pic.name}" src="https://testimg.quanyou.com.cn/${pic.picturePath}"/>`
        )
      }
    },
    /**
     * 初始化编辑器
     */
    initEidtor() {
      /* Scriptjs(process.env.BASE_URL + '/cdn/tinymce/tinymce.min.js',
        () => { */
      let defaultToolBar = 'undo redo removeformat | cut copy paste | fontsizeselect forecolor backcolor | bold italic underline strikethrough | alignleft alignright aligncenter alignjustify alignnone | bullist numlist  | outdent indent blockquote  table | link unlink hr emoticons | image axupimgs media |  code  codesample  fullscreen preview '
      // let defaultToolBar = 'undo redo removeformat | cut copy paste | fontsizeselect forecolor backcolor | bold italic underline strikethrough | alignleft alignright aligncenter alignjustify alignnone | bullist numlist  | outdent indent blockquote  codesample  table | link unlink hr | image axupimgs media | emoticons  code  fullscreen preview '
      const customToolBar = this.customButtons.map(v => {
        return v.code
      })
      if (customToolBar.length) {
        defaultToolBar = `${defaultToolBar} | ${customToolBar.join(' ')}`
      }

      const defaultConfig = {
        selector: '#' + this.editorId,
        height: 500,
        language_url: `${process.env.BASE_URL}/cdn/tinymce/langs/zh_CN.js`,
        skin_url: `${process.env.BASE_URL}/cdn/tinymce/skins/ui/oxide`,
        emoticons_database_url: `${process.env.BASE_URL}/cdn/tinymce/emoticons/js/emojis`,

        language: 'zh_CN',
        menubar: true, // 设置富文本编辑器菜单, 默认true
        // menubar: 'file edit insert view format table tools help', // （1级菜单）最上方的菜单
        branding: false, // 是否关闭底部官网提示（右下角技术支持）水印“Powered by TinyMCE” , 默认true
        statusbar: true, // 显示底部状态栏 默认true

        readonly: false, // 设置只读属性 默认 false
        resize: false, // 调节编辑器大小 默认 true

        autosave_ask_before_unload: false, // 当关闭或跳转URL时，弹出提示框提醒用户仍未保存变更内容。 默认 true  需引入插件autosave
        autosave_interval: '3s', // 设置自动保存为草稿时间 单位只能为s 需引入插件autosave
        autosave_prefix: `editor_${this.$route.path}`, // 设置自动保存为草稿时前缀 本地localStorage中存储  需引入插件autosave
        autosave_retention: '300m', // 自动草稿的有效期 单位只能为m  需引入插件autosave

        browser_spellcheck: true, // 拼写检查
        elementpath: true, // 是否显示编辑器底部左侧元素路径

        image_advtab: true, // 为上传图片窗口添加高级属性 需引入插件image
        paste_data_images: true, // 粘贴data格式的图像 需引入插件paste 谷歌浏览器无法粘贴
        paste_as_text: true, // 默认粘贴为文本 需引入插件paste 谷歌浏览器无法粘贴

        contextmenu: 'copy paste cut link', // 上下文菜单 默认 false
        draggable_modal: true, // 模态框拖动 默认false
        placeholder: this.placeholder, // 占位符
        // theme: 'silver', // 主题 必须引入
        // content_css: `/tinymce/skins/content/default`, // 富文本编辑器内容区域样式
        content_style: 'body, p{font-size: 14px}', // 为内容区编辑自定义css样式
        fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px', // 工具栏自定义字体大小选项
        font_formats: "微软雅黑='微软雅黑'; 宋体='宋体'; 黑体='黑体'; 仿宋='仿宋'; 楷体='楷体'; 隶书='隶书'; 幼圆='幼圆'; 方正舒体='方正舒体'; 方正姚体='方正姚体'; 等线='等线'; 华文彩云='华文彩云'; 华文仿宋='华文仿宋'; 华文行楷='华文行楷'; 华文楷体='华文楷体'; 华文隶书='华文隶书'; Andale Mono=andale mono,times; Arial=arial; Arial Black=arial black;avant garde; Book Antiqua=book antiqua;palatino; Comic Sans MS=comic sans ms; Courier New=courier new;courier; Georgia=georgia; Helvetica=helvetica; Impact=impact;chicago; Symbol=symbol; Tahoma=tahoma;arial; sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms; Verdana=verdana;geneva; Webdings=webdings; Wingdings=wingdings", // 工具栏自定义字体选项
        toolbar_sticky: true, // 粘性工具栏 默认false (在向下滚动网页直到不再可见编辑器时，将工具栏和菜单停靠在屏幕顶部)
        toolbar_mode: 'sliding', // sliding生效条件toolbar必须为字符串,且有'|'区分,不能为数组
        fullscreen_native: true, // 设为true后，全屏将不在是网页全屏，而是使用浏览器的全屏模式（同按F11键）,需引入插件fullscreen
        mediaembed_max_width: 450,
        plugins: 'autosave,advlist,image,imagetools,axupimgs,media,link,table,fullscreen,preview,lists,wordcount,code,codesample,emoticons,hr', // mediaembed(增强媒体嵌入),advcode(高级代码)
        toolbar: defaultToolBar, // | myCustomToolbarButton myCustomMedia submit clearContent
        imagetools_cors_hosts: ['quanyou.com.cn', 'baidu.com'], // 配置图片编辑时，图片来源跨域白名单
        imagetools_proxy: 'proxy.php', // 后端配置参照： http://tinymce.ax-z.cn/plugins/imagetools.php
        imagetools_toolbar: 'rotateleft rotateright | flipv fliph | imageoptions', // 移除编辑 editimage
        media_alt_source: false, // 显示隐藏资源备用地址输入框
        // 初始化前执行
        setup: (editor) => {
          // 监听鼠标按下事件
          editor.on('keydown', (e) => {
            if (e.keyCode === 9) {
              if (e.shiftKey) {
                editor.execCommand('Outdent')
              } else {
                editor.execCommand('Indent')
              }
              e.preventDefault()
              e.stopPropagation()
            }
          })
          // 注册自定义操作按钮
          this.customButtons.forEach(v => {
            if (v.code) {
              const btnObj = {
                text: v.text,
                onAction: (editor) => {
                  v.onAction && v.onAction(editor)
                }
              }
              if (v.tooltip) {
                btnObj.tooltip = v.tooltip
              }
              editor.ui.registry.addButton(v.code, btnObj)
            }
          })
          /* // 注册自定义上传按钮
              editor.ui.registry.addButton('myCustomToolbarButton', {
                text: '<i class="el-icon-upload" style="font-size: 24px"></i>',
                tooltip: '从图库选择图片',
                onAction: () => {
                  this.openPicStock()
                }
              })*/
        },
        // 设置事件回调函数 ,初始化结束后执行
        init_instance_callback: (editor) => {
          // 里面实现双向数据绑定功能
          if (this.value) {
            editor.setContent(this.value)
          }
          this.editor = editor
          this.hasInit = true
          editor.on('Input undo redo Change execCommand SetContent', (e) => {
            this.hasChange = true
            // editor.getContent({ format: ''text }) // 获取纯文本
            this.$emit('change', editor.getContent())
          })
        },
        // 图片回调url的相对路径，可写可不写，为兼容图片回调而设
        image_prepend_url: `${this.filePrefix}`, // 指定图片URL补齐前缀。仅image使用
        // images_upload_base_path: `${this.filePrefix}`, // 全局使用： 图片回调url的相对路径，可写可不写，为兼容图片回调而设。
        axupimgs_filetype: this.imageAccept.join(','), // 设置允许上传的图片类型
        // 自定义图片上传逻辑
        images_upload_handler: (blobInfo, success, failure) => {
          const file = blobInfo.blob()
          const contentType = file.type // 文件类型：image/png
          const suffix = contentType.split('/')[1] // 文件后缀
          if (!this.imageAccept.includes(`.${suffix}`)) {
            this.$message.warning({
              message: `上传资源只能是${this.imageAccept.join(',')} 格式!`,
              duration: 5000,
              customClass: 'zZindex'
            })
            return false
          }
          this.uploadToServer('image', file, success, failure)
        },

        file_picker_types: 'image media', // 'file image media' 只允许图片和视频自定义上传则去掉file,加上file则链接也可上传
        // 自定义文件上传逻辑
        file_picker_callback: (callback, value, meta) => {
          // const metaFieldname = meta.fieldname // 插件字段名称
          const metaType = meta.filetype // 插件操作类型
          // console.log(meta, value)
          // 文件分类
          let filetype = this.fileAccept.concat(this.videoAccept).join(',')
          // '.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4'
          // 为不同插件指定文件类型
          switch (metaType) {
            case 'image':
              filetype = this.imageAccept.join(',') // '.jpg, .jpeg, .png, .gif'
              // filetype = 'image/gif,image/jpeg,image/png,image/jpg'
              break
            case 'media':
              filetype = this.videoAccept.join(',') // '.mp3, .mp4,.ogv'
              // filetype = 'video/mp4,audio/mpeg,video/ogg,video/AVI,video/mov,video/FLV,video/rmvb,video/mtv,video/mpeg'
              break
            case 'file':
              filetype = this.fileAccept.join(',')
              break
            default:
          }
          // 模拟一个input用于添加本地文件
          var input = document.createElement('input')
          input.setAttribute('type', 'file')
          input.setAttribute('accept', filetype)
          input.setAttribute('id', 'inputfile')
          input.click()
          input.onchange = (e) => {
            const cInput = e.path[0]
            const file = cInput.files[0] // 文件
            const fileName = file.name
            const contentType = file.type // 文件类型：image/png 、 video/mp4
            const nameDots = fileName.split('.')
            const suffix = nameDots[nameDots.length - 1] // 文件后缀
            console.log(fileName, contentType, suffix, filetype.indexOf(suffix))
            if (filetype.indexOf(suffix) === -1) {
              this.$message.warning({
                message: `上传资源只能是${filetype} 格式!`,
                duration: 5000,
                customClass: 'zZindex'
              })
              return false
            }

            this.uploadToServer(metaType, file, (url) => {
              // 上传成功
              url = formatURL(url, this.filePrefix)
              if (metaType === 'file') {
                callback(url, { text: file.name })
              } else if (metaType === 'image') {
                callback(url, { alt: file.name })
              } else if (metaType === 'media') {
                // callback(url, { source2: 'alt.ogg', poster: 'image.jpg' })
                callback(url, { source2: '', poster: '' })
              }
            }, (error) => {
              // 上传失败
              console.log('Error: ' + error)
              callback(file, { alt: file.name })
            })

            // 下方被注释掉的是官方的一个例子,供参考
            /* var reader = new FileReader();
                    reader.onload = function () {
                        // Note: Now we need to register the blob in TinyMCEs image blob
                        // registry. In the next release this part hopefully won't be
                        // necessary, as we are looking to handle it internally.
                        var id = 'blobid' + (new Date()).getTime();
                        var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                        var base64 = reader.result.split(',')[1];
                        var blobInfo = blobCache.create(id, file, base64);
                        blobCache.add(blobInfo);

                        // call the callback and populate the Title field with the file name
                        callback(blobInfo.blobUri(), { title: file.name });
                    };
                    reader.readAsDataURL(file);*/
          }
        }
      }
      const conf = Object.assign(defaultConfig, this.editorConfig)
      this.editConfig = conf
      tinymce.init(conf)
        .then((editors) => {
          this.editor = editors[0]
          if (this.content || this.initContent) {
            this.setContent(this.content || this.initContent)
          }
          // this.editor.show()
          this.setMode(this.mode)
        })
        .catch((error) => {
          this.$message.error('富文本编辑器初始化失败: ' + error)
        })
      /* }
      ) */
    },
    /**
     * 上传文件至服务器
     * @param {string} metaType 上传类型：image 图片 ，video 视频 , file 文件
     * @param {file} file 需上传的文件
     * @param {Function} success 上传成功的回调
     * @param {Function} failure 上传失败的回调
    */
    uploadToServer(metaType, file, success, failure) {
      if (metaType === 'image') {
        // 若为上传图片，限制上传大小
        const isLt2M = file.size / 1024 / 1024 <= this.imageSize
        if (!isLt2M) {
          this.$notify.error({
            title: '错误',
            offset: 100,
            message: `上传图片大小不能超过 ${this.imageSize}MB!`
          })
          return false
        }
      }
      const flag = this.beforeUpload(metaType, file)
      if (!flag) {
        return false
      }

      const formData = new FormData() // 创建form表单
      formData.append('file', file)
      for (const key in this.uploadData) { // 添加其他form表单数据
        formData.append(key, this.uploadData[key])
      }
      const loading = this.$loading({
        lock: true,
        text: '正在上传...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      })
      request.post(this.uploadUrl, formData).then((result) => {
        const url = (result.data && result.data[this.uploadResField]) || ''
        if (!url) {
          this.$message.error({
            message: `上传结果异常!`,
            duration: 3000
          })
          return
        }
        success(url)
        loading.close()
      }).catch((error) => {
        failure('Error: ' + error)
        this.$message.error('上传失败')
        loading.close()
      }).finally((ff) => {
        loading.close()
      })
    },

    /**
     * 插入内容
     * @param {string} type 内容类型： image 图片, video 视频，audio 音频
     * @param {string} content 内容/文件地址
     * */
    insertContent(type, content) {
      /* // 光标放最后?
      this.editor.execCommand('selectAll')
      this.editor.selection.getRng().collapse(false)
      this.editor.focus() */

      let htmlContent = content
      if (type === 'image') {
        htmlContent = `<img src="${content}" style="max-width:100%;"/>`
      } else if (type === 'video') {
        htmlContent = `<video src="${content}" 
            webkit-playsinline="true" 
            x5-video-player-type="h5" 
            x5-video-orientation="portraint" 
            controls="controls" style="max-width:100%"></video><p><br></p>`
      } else if (type === 'audio') {
        htmlContent = `<audio src="${content}" controls="controls" style="max-width:100%"></audio><p><br></p>`
      }
      this.editor.insertContent(htmlContent)
    },
    /**
     * 获取编辑器中的html代码
     * @return {*}
     */
    getContent() {
      return this.editor.getContent()
    },
    /**
     * 获取编辑器中的纯文本
     * @return {*}
     */
    getText() {
      return this.editor.getContent({ format: 'text' })
    },
    /**
     * 保存并获取编辑器中的html代码
     * @return {*}
     */
    save() {
      return this.editor.save()
    },
    /**
     * 设置富文本编辑器展示的内容
     * @param content
     */
    setContent(content) {
      if (this.editor) {
        this.editor.setContent(content)
      } else {
        console.warn('编辑器尚未初始化')
      }
    },
    /**
     * 设置富文本编辑器模式
     * @param mode 模式值，readonly(只读)/design(编辑)
     */
    setMode(mode) {
      if (this.editor) {
        this.editor.setMode(mode)
      } else {
        console.warn('编辑器尚未初始化')
      }
    },
    // 光标放最后
    goEnd() {
      const editor = this.editor
      editor.execCommand('selectAll')
      editor.selection.getRng().collapse(false)
      editor.focus()
    },
    // 显示编辑器
    show() {
      this.editor.show()
    },
    // 隐藏编辑器
    hide() {
      this.editor.hide()
    }
  }
}
</script>

<style lang='scss' scoped>

</style>
